package org.bugeden.netdisk.transfer

import org.bugeden.netdisk.user.IUser
import javax.mail.search.SearchTerm
import org.bugeden.netdisk.transfer.IDevice.{NotBindingException, UnExistBlockException}
import javax.mail._
import org.apache.commons.io.IOUtils
import org.apache.commons.codec.digest.DigestUtils
import com.google.gson.Gson
import java.util.{Date, Properties}
import org.apache.commons.mail.{MultiPartEmail, EmailAttachment}
import java.io.{FileOutputStream, File, InputStream}

/**
 * Created by IntelliJ IDEA.
 * User: inotseeyou
 * Date: 2010-11-7
 * Time: 0:53:08
 * To change this template use File | Settings | File Templates.
 */

//todo just support gmail first times
class MailBox extends AnyRef with IDevice {
  import MailBox._

  private var session: Session = null
  private var user: IUser = null
  private var gson = new Gson
  private var tempDir = "tmpdata"
  this.setTempDir("tmpdata")

  def setTempDir(dir: String): Unit = {
    var file = new File(dir)
    if (!file.isDirectory) {
      file.mkdirs
    }
    this.tempDir = dir
  }

  def bind(user: IUser): Unit = {
    this.user = user
  }

  def unbind(): Unit = throw new UnsupportedOperationException

  def flush(): Unit = null

  def update(block: Block): Block = {
    this.remove(block)
    this.write(block)
  }

  def isBlockExist(block: Block): Boolean = {
    val msgs = this.getMessageBySubject(block.id)
    if (msgs.isEmpty) false else true
  }

  def remove(block: Block): Block = {
    val msgs = this.getMessageBySubject(block.id)
    for (msg <- msgs) {
      msg.setFlag(Flags.Flag.DELETED, true)
    }
    block
  }

  @throws(classOf[NotBindingException])
  def read(block: Block): Option[Block] = {
    val msgs = this.getMessageBySubject(block.id)
    if (msgs.isEmpty) {
      None
    } else {
      Some(this.blockFromMessages(msgs))
    }
  }

  def getMessageBySubject(subject: String): Array[Message] = {
    val store = this.getStore()
    val inbox = store.getFolder("INBOX")
    inbox.open(Folder.READ_WRITE)
    val msgs = inbox.search(titleTerm(subject))
    msgs
  }

  private def blockFromMessages(msgs: Array[Message]): Block = {
    var blockToReturn: Block = null
    for (msg <- msgs) {
      val content = msg.getContent
      val part = content.asInstanceOf[Multipart]
      val result = parseMultiPart(part)
      val block = new Block(msg.getSubject)
      block.desc(result.textContent)
      block.createdDate(msg.getReceivedDate)
      if (blockToReturn == null ||
              block.createdDate.getTime > blockToReturn.createdDate.getTime) {
        blockToReturn = block
      }
      for (is <- result.inputStreams) {
        try {
          block.data(IOUtils.toByteArray(is))
        } catch {
          case _ => block.data(Array[Byte]())
        }
      }
    }
    blockToReturn
  }

  def write(block: Block): Block = {
    val subject = block.id
    val desc = block.desc
    val attach = new EmailAttachment
    attach.setName(block.id)
    attach.setDescription(desc)
    attach.setPath(getBlockFile(block).getAbsolutePath)

    val email = new MultiPartEmail()
    email.setHostName(user.sendHostName)
    email.setFrom(user.userName)
    email.setSmtpPort(user.sendHostPort)
    email.setSubject(subject)
    email.setMsg(desc)
    email.setTLS(true)
    email.setAuthentication(user.userName, user.password)
    email.addTo(user.userName)

    email.attach(attach)

    email.send
    block
  }

  private def getBlockFile(block: Block): File = {
    val dir = new File(this.tempDir)
    val file = new File(dir, "%s.%s".format("tmp", block.id.replaceAll("[^0-9a-zA-Z]", ".")))
    if (file.isFile) {
      file
    } else {
      if (!file.isFile) file.createNewFile
      val fos = new FileOutputStream(file)
      IOUtils.write(block.data, fos)
      file
    }
  }

  private def getStore(): Store = {
    val props = System.getProperties
    val session = Session.getInstance(props, null)
    val store = session.getStore(user.revProtocol)
    store.connect(user.revHostName, user.userName, user.password)
    store
  }

}

object MailBox {
  private val titleSearchTerm = new TitleSearchTerm("")

  private def titleTerm(title: String): SearchTerm = {
    this.titleSearchTerm.setTitle(title)
  }

  private class TitleSearchTerm(var title: String) extends SearchTerm {
    def setTitle(t: String): TitleSearchTerm = {
      this.title = t
      this
    }

    def `match`(msg: Message) = msg.getSubject.equalsIgnoreCase(title)
  }

  def parseMultiPart(mp: Multipart): ParsedResult = {
    val count = mp.getCount
    val result = new ParsedResult
    for (i <- 0 until count) {
      val body = mp.getBodyPart(i)
      val bodyType = body.getContentType.toLowerCase
      bodyType match {
        case t if t.startsWith("multipart") => {
          //todo combine array function
          val rs = this.parseMultiPart(body.getContent.asInstanceOf[Multipart])
          result.textContent = rs.textContent
          val len1 = rs.inputStreams.length
          val len2 = result.inputStreams.length
          val inputStreams = new Array[InputStream](len1 + len2)
          rs.inputStreams.copyToArray(inputStreams, 0, len1)
          result.inputStreams.copyToArray(inputStreams, len1, len1 + len2)
          result.inputStreams = inputStreams
        }
        case t if t.startsWith("text/plain") =>
          result.textContent = body.getContent.asInstanceOf[String]
        case t if t.indexOf("stream") > -1 => {
          val is = body.getContent.asInstanceOf[InputStream]
          val len = result.inputStreams.length
          val inputStreams = new Array[InputStream](len + 1)
          result.inputStreams.copyToArray(inputStreams)
          inputStreams(len) = is
          result.inputStreams = inputStreams
        }
        case _@en =>
      //todo log this: throw new UnsupportedOperationException("unknown type[%s]".format(bodyType))
      }
    }
    result
  }

  class ParsedResult {
    var textContent = ""
    var inputStreams = Array[InputStream]()
  }
}
